﻿
namespace HSS.ComponentModel
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Linq;
	using System.Reflection;
	using System.Security;
	using System.Windows;

	#endregion

	#region TypeDescriptor
	/// <summary>
	/// TypeDescriptor
	/// </summary>
	public static class TypeDescriptor
	{
		private static Dictionary<Type, TypeConverter> _cachedTypeConverterForType;
		private static Dictionary<object, TypeConverter> _cachedTypeConverterForMember;

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
		static TypeDescriptor()
		{
			_cachedTypeConverterForType = new Dictionary<Type, TypeConverter>();
			_cachedTypeConverterForMember = new Dictionary<object, TypeConverter>();
		}

		/// <summary>
		/// Looks up a Converter by instance
		/// </summary>
		/// <param name="instance">The <see cref="Object"/> instance to parse</param>
		/// <returns>The <see cref="TypeConverter"/> if found; otherwise null.</returns>
		public static TypeConverter GetConverter(object instance)
		{
			Guard.ArgumentNotNull(instance, "instance");
			TypeConverter converter = null;
			converter = GetConverter(instance.GetType());
			return converter;
		}
		/// <summary>
		/// Looks up a Converter by PropertyInfo
		/// </summary>
		/// <param name="pi">The <see cref="PropertyInfo"/> object to parse</param>
		/// <returns>The <see cref="TypeConverter"/> if found; otherwise null.</returns>
		public static TypeConverter GetConverter(PropertyInfo pi)
		{
			TypeConverter converter = null;
			converter = GetTypeConverterForProperty(pi);
			return converter;
		}
		/// <summary>
		/// Looks up a Converter by Type
		/// </summary>
		/// <param name="type">The <see cref="Type"/> to parse</param>
		/// <returns>The <see cref="TypeConverter"/> if found; otherwise null.</returns>
		public static TypeConverter GetConverter(Type type)
		{
			TypeConverter converter = null;
			converter = GetCoreConverterFromType(type);
			if (converter == null)
				converter = GetCustomConverterForType(type);
			return converter;
		}

		/// <summary>
		/// Parses an Object's instance properties and returns a List of HSSPropertyDescriptor
		/// </summary>
		/// <param name="instance">The object instance to parse</param>
		/// <returns>A List of Type HSSPropertyDescriptor</returns>
		public static PropertyDescriptorCollection GetProperties(object instance)
		{
			if (null == instance)
				return new PropertyDescriptorCollection(null);
			return GetProperties(instance.GetType());
		}
		/// <summary>
		/// Parses a Types properties and returns a List of HSSPropertyDescriptor
		/// </summary>
		/// <param name="type">The Type to parse</param>
		/// <returns>A List of Type HSSPropertyDescriptor</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		public static PropertyDescriptorCollection GetProperties(Type type)
		{
			if (null == type)
				return new PropertyDescriptorCollection(null);

			var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
			var descriptors = new HashSet<HSSPropertyDescriptor>();
			for (int i = 0; i < properties.Length; i++)
			{
				try
				{
					var d = GetProperty(properties[i]);
					if (null != d)
						descriptors.Add(d);
				}
				catch { }
			}
			return new PropertyDescriptorCollection(descriptors.ToArray());
		}
		/// <summary>
		/// Parses a Types properties and returns a HSSPropertyDescriptor for the
		/// requested Property.
		/// </summary>
		/// <param name="type">The Type that contains the requested property.</param>
		/// <param name="propertyName">The name of the Property to retreive.</param>
		/// <returns>A HSSPropertyDescriptor</returns>
		public static HSSPropertyDescriptor GetProperty(Type type, string propertyName)
		{
			if (null == type)
				return null;

			if (string.IsNullOrEmpty(propertyName))
				return null;

			var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
			if (null == property)
				return null;

			return GetProperty(property);
		}
		private static HSSPropertyDescriptor GetProperty(PropertyInfo property)
		{
			// Skip Properties that have indexers Get/Set (i.e., Public char this[index]{ get; }
			var paramInfo = property.GetIndexParameters();
			if (null != paramInfo && paramInfo.Length > 0)
				return null;

			// Skip Properties with SecurityCritical Attribute
			var getMethod = property.GetGetMethod(false);
			var securityCritical = false;
			var s = GetAttribute<SecurityCriticalAttribute>(getMethod);
			if (null != s)
				securityCritical = true;
			if (securityCritical)
				return null;

			var isBrowsable = true;
			var b = GetAttribute<BrowsableAttribute>(property);
			if (null != b)
				isBrowsable = b.Browsable;
			if (isBrowsable)
			{
				var eb = GetAttribute<EditorBrowsableAttribute>(property);
				if (null != eb && (eb.State == EditorBrowsableState.Never || eb.State == EditorBrowsableState.Advanced))
					isBrowsable = false;
			}
			if (isBrowsable)
				return new HSSPropertyDescriptor(property);

			return null;
		}

		/// <summary>
		/// Get the specified attribute
		/// </summary>
		/// <typeparam name="T">The Type of attribute to retrieve</typeparam>
		/// <param name="memberInfo">The PropertyInfo to evaluate</param>
		/// <returns>The request attribute</returns>
		public static IEnumerable<T> GetAttributes<T>(MemberInfo memberInfo) where T : Attribute
		{
			return GetAttributes<T>(memberInfo, true);
		}
		/// <summary>
		/// Get the specified attribute
		/// </summary>
		/// <typeparam name="T">The Type of attribute to retrieve</typeparam>
		/// <param name="memberInfo">The PropertyInfo to evaluate</param>
		/// <param name="inherit"> true to search this member's inheritance chain to find the attributes; otherwise, false.</param>
		/// <returns>The request attribute</returns>
		public static IEnumerable<T> GetAttributes<T>(MemberInfo memberInfo, bool inherit) where T : Attribute
		{
			Guard.ArgumentNotNull(memberInfo, "memberInfo");
			var attributes = memberInfo.GetCustomAttributes(typeof(T), inherit);
			return (attributes.Length > 0) ? attributes.OfType<T>() : default(T[]);
		}

		/// <summary>
		/// Get the specified attribute
		/// </summary>
		/// <typeparam name="T"> The type of attribute to search for. Only attributes that are assignable to this type are returned.</typeparam>
		/// <param name="memberInfo">The MethodInfo to evaluate</param>
		/// <returns>The request attribute</returns>
		public static T GetAttribute<T>(MemberInfo memberInfo) where T : Attribute
		{
			return GetAttribute<T>(memberInfo, true);
		}
		/// <summary>
		/// Get the specified attribute
		/// </summary>
		/// <typeparam name="T"> The type of attribute to search for. Only attributes that are assignable to this type are returned.</typeparam>
		/// <param name="memberInfo">The MethodInfo to evaluate</param>
		/// <param name="inherit"> true to search this member's inheritance chain to find the attributes; otherwise, false.</param>
		/// <returns>The request attribute</returns>
		public static T GetAttribute<T>(MemberInfo memberInfo, bool inherit) where T : Attribute
		{
			Guard.ArgumentNotNull(memberInfo, "memberInfo");
			var attributes = memberInfo.GetCustomAttributes(typeof(T), inherit);
			return (attributes.Length > 0) ? attributes.OfType<T>().FirstOrDefault() : default(T);
		}

		/// <summary>
		/// Sort a collection of <see cref="HSSPropertyDescriptor"/>s.
		/// </summary>
		/// <param name="descriptors">The collection of <see cref="HSSPropertyDescriptor"/>s.</param>
		public static void SortDescriptorArray(IEnumerable<HSSPropertyDescriptor> descriptors)
		{
			if (descriptors == null)
				throw new ArgumentNullException("descriptors");
			descriptors.OrderBy((descriptor) => { return descriptor; }, HSSPropertyDescriptor.Comparer);
		}

		private static TypeConverter GetTypeConverterForProperty(PropertyInfo pinfo)
		{
			if (pinfo == null)
				return null;

			TypeConverter typeConverterByName = null;
			object[] customAttributes = null;

			// If not cached, check Property for TypeConverter
			if (!_cachedTypeConverterForMember.TryGetValue(pinfo, out typeConverterByName))
			{
				customAttributes = pinfo.GetCustomAttributes(typeof(TypeConverterAttribute), true);
				if ((customAttributes != null) && (customAttributes.Length > 0))
					typeConverterByName = GetTypeConverterByName(((TypeConverterAttribute)customAttributes[0]).ConverterTypeName);
				if (null != typeConverterByName)
					_cachedTypeConverterForMember[pinfo] = typeConverterByName;
			}

			// If still null, check Property Type for TypeConverter
			if (typeConverterByName == null)
				typeConverterByName = GetConverter(pinfo.PropertyType);

			return typeConverterByName;
		}
		private static TypeConverter GetCoreConverterFromType(Type type)
		{
			TypeConverter converter = null;
			if (type == typeof(int))
			{
				return new Int32Converter();
			}
			if (type == typeof(short))
			{
				return new Int16Converter();
			}
			if (type == typeof(long))
			{
				return new Int64Converter();
			}
			if (type == typeof(uint))
			{
				return new UInt32Converter();
			}
			if (type == typeof(ushort))
			{
				return new UInt16Converter();
			}
			if (type == typeof(ulong))
			{
				return new UInt64Converter();
			}
			if (type == typeof(bool))
			{
				return new BooleanConverter();
			}
			if (type == typeof(bool?))
			{
				return new NullableBoolConverter();
			}
			if (type == typeof(double))
			{
				return new DoubleConverter();
			}
			if (type == typeof(float))
			{
				return new SingleConverter();
			}
			if (type == typeof(byte))
			{
				return new ByteConverter();
			}
			if (type == typeof(sbyte))
			{
				return new SByteConverter();
			}
			if (type == typeof(char))
			{
				return new CharConverter();
			}
			if (type == typeof(decimal))
			{
				return new DecimalConverter();
			}
			if (type == typeof(TimeSpan))
			{
				return new TimeSpanConverter();
			}
			if (type == typeof(Guid))
			{
				return new GuidConverter();
			}
			if (type == typeof(Thickness))
			{
				return new ThicknessConverter();
			}
			if (type == typeof(DateTime))
			{
				return new DateTimeConverter();
			}
			if (typeof(Enum).IsAssignableFrom(type))
			{
				return new EnumConverter(type);
			}
			if (type == typeof(string))
			{
				return new StringConverter();
			}
			return converter;
		}
		private static TypeConverter GetCustomConverterForType(Type propertyType)
		{
			TypeConverter typeConverterByName = null;
			// If not cached, check for TypeConverter
			if (!_cachedTypeConverterForType.TryGetValue(propertyType, out typeConverterByName))
			{
				object[] customAttributes = propertyType.GetCustomAttributes(typeof(TypeConverterAttribute), true);
				if ((customAttributes == null) || (customAttributes.Length <= 0))
					return null;
				else
					typeConverterByName = GetTypeConverterByName(((TypeConverterAttribute)customAttributes[0]).ConverterTypeName);
				_cachedTypeConverterForType[propertyType] = typeConverterByName;
			}

			return typeConverterByName;
		}
		private static TypeConverter GetTypeConverterByName(string converterTypeName)
		{
			return (Activator.CreateInstance(Type.GetType(converterTypeName)) as TypeConverter);
		}
	}
	#endregion
}
